#![allow(non_snake_case, dead_code)]
//! The Reminder Rust Client crate
//!
//! * [**'ReminderClient'**](./struct.ReminderClient.html) Repetition periods
//! * [**'Deltas'**](./struct.Deltas.html) Repetition periods
//! * [**'Interval'**](./struct.Interval.html) Info about where you need to repeat Record
//! * [**'Record'**](./struct.Record.html) Record object what you need to remember
//! * [**'RecordCreationDto'**](./struct.RecordCreationDto.html) The DTO for creating new record
//! * [**'Page'**](./struct.Page.html) The page object with elements

use chrono::{DateTime, FixedOffset};
use reqwest::blocking::{Client, ClientBuilder};
use serde::{Deserialize, Serialize};
use std::error::Error;

const DATE_FORMAT: &'static str = "yyyy-mm-dd hh:mm:ss.ffffff";

/// Repetition periods
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Deltas {
    name: String,
    deltas: Vec<u64>,
}

/// Info about where you need to repeat Record
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Interval {
    completed: bool,
    startDate: String,
    nextDate: String,
    deltas: Deltas,
}

/// Record object what you need to remember
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Record {
    id: u64,
    name: String,
    description: Option<String>,
    interval: Interval,
}

/// The DTO for creating new record
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct RecordCreationDto {
    name: String,
    description: Option<String>,
    deltasName: String,
}

/// The page object with elements
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Page<T> {
    content: Vec<T>,
    totalPages: u32,
    totalElements: u32,
    size: u32,
    number: u32,
}

/// REST API client for reminderSpring server
#[derive(Clone)]
pub struct ReminderClient {
    client: Client,
    baseUrl: String,
}

impl RecordCreationDto {
    pub fn new(name: String, description: Option<String>, deltasName: String) -> RecordCreationDto {
        RecordCreationDto {
            name,
            description,
            deltasName,
        }
    }

    /// Name of new record
    pub fn name(&self) -> &str {
        &self.name
    }

    /// Optional description of new record
    pub fn description(&self) -> &Option<String> {
        &self.description
    }

    /// The name of existing deltas from database
    pub fn deltasName(&self) -> &str {
        &self.deltasName
    }
}

impl Interval {
    /// If record is complete this variable is true
    pub fn isCompleted(&self) -> &bool {
        &self.completed
    }

    /// The date of creating record
    pub fn startDate(&self) -> DateTime<FixedOffset> {
        DateTime::parse_from_str(&self.startDate, DATE_FORMAT).unwrap()
    }

    /// The date when you need to repeat record
    pub fn nextDate(&self) -> DateTime<FixedOffset> {
        DateTime::parse_from_str(&self.nextDate, DATE_FORMAT).unwrap()
    }

    /// Deltas object for this interval
    pub fn deltas(&self) -> &Deltas {
        &self.deltas
    }
}

impl Record {
    /// The id of record in database
    pub fn id(&self) -> &u64 {
        &self.id
    }

    /// The not unique name of record
    pub fn name(&self) -> &str {
        &self.name
    }

    /// Optional description
    pub fn description(&self) -> &Option<String> {
        &self.description
    }

    /// Interval object
    pub fn interval(&self) -> &Interval {
        &self.interval
    }
}

impl Deltas {
    /// Name of the object and id in database
    pub fn name(&self) -> &str {
        &self.name
    }

    /// The periods of repeat
    pub fn deltas(&self) -> &Vec<u64> {
        &self.deltas
    }
}

impl<T> Page<T> {
    /// The list of elements on page
    pub fn content(&self) -> &Vec<T> {
        &self.content
    }

    /// Total number of pages
    pub fn totalPages(&self) -> &u32 {
        &self.totalPages
    }

    /// Total number of elements
    pub fn totalElement(&self) -> &u32 {
        &self.totalElements
    }

    /// Number of elements of page
    pub fn size(&self) -> &u32 {
        &self.size
    }

    /// Page number
    pub fn number(&self) -> &u32 {
        &self.number
    }
}

impl ReminderClient {
    pub fn new() -> ReminderClient {
        ReminderClient {
            client: ClientBuilder::new().build().unwrap(),
            baseUrl: "http://localhost:8080".to_string(),
        }
    }

    /// Return Record with specified id
    pub fn getRecord(&self, id: u64) -> Result<Record, Box<dyn Error>> {
        let response = self
            .client
            .get(format!("{}{}{}", self.baseUrl, "/record/", id))
            .send()?;
        let json = response.text()?;
        let record: Record = serde_json::from_str(&json)?;
        Ok(record)
    }

    /// Return Page of Record with specified page number and number of elements on it
    pub fn getRecordsPage(&self, page: u32, size: u32) -> Result<Page<Record>, Box<dyn Error>> {
        let response = self
            .client
            .get(format!(
                "{}{}?page={}&size={}",
                self.baseUrl, "/record/all", page, size
            ))
            .send()?;
        let json = response.text()?;
        let page: Page<Record> = serde_json::from_str(&json)?;
        Ok(page)
    }

    /// Create new Record in database from RecordCreationDto
    pub fn newRecord(&self, record: RecordCreationDto) -> Result<Record, Box<dyn Error>> {
        let json = serde_json::to_string(&record)?;
        let response = self
            .client
            .post(format!("{}/record/new", self.baseUrl))
            .body(json)
            .header("Content-Type", "application/json")
            .send()?;
        let json = response.text()?;
        let record: Record = serde_json::from_str(&json)?;
        Ok(record)
    }

    /// Delete Record with specified id from database
    pub fn deleteRecord(&self, id: u64) -> Result<(), reqwest::Error> {
        self.client
            .delete(format!("{}/record?id={}", self.baseUrl, id))
            .send()?;
        Ok(())
    }

    /// Return Deltas with specified name from database
    pub fn getDeltas(&self, name: &str) -> Result<Deltas, Box<dyn Error>> {
        let response = self
            .client
            .get(format!("{}/deltas/{}", self.baseUrl, name))
            .send()?;
        let json = response.text()?;
        let deltas: Deltas = serde_json::from_str(&json)?;
        Ok(deltas)
    }

    /// Return Page of Deltas with specified page number and number of elements on it
    pub fn getDeltasPage(&self, page: u32, size: u32) -> Result<Page<Deltas>, Box<dyn Error>> {
        let response = self
            .client
            .get(format!(
                "{}{}?page={}&size={}",
                self.baseUrl, "/deltas/all", page, size
            ))
            .send()?;
        let json = response.text()?;
        let page: Page<Deltas> = serde_json::from_str(&json)?;
        Ok(page)
    }

    /// Create new Deltas in database from Deltas object
    pub fn newDeltas(&self, deltas: Deltas) -> Result<Deltas, Box<dyn Error>> {
        let json = serde_json::to_string(&deltas).unwrap();
        let response = self
            .client
            .post(format!("{}/deltas/new", self.baseUrl))
            .body(json)
            .header("Content-Type", "application/json")
            .send()?;
        let json = response.text()?;
        let deltas_new: Deltas = serde_json::from_str(&json)?;
        Ok(deltas_new)
    }

    /// Delete Deltas with specified name from database
    pub fn deleteDeltas(&self, name: &str) -> Result<(), reqwest::Error> {
        self.client
            .delete(format!("{}/deltas?name={}", self.baseUrl, name))
            .send()?;
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn CRUD_test() -> Result<(), Box<dyn Error>> {
        let record_dto =
            RecordCreationDto::new("some record".to_string(), None, "default".to_string());
        let client = ReminderClient::new();
        let record = client.newRecord(record_dto)?;

        let id = record.id;
        let _record = client.getRecord(id)?;

        let page = 0;
        let size = 10;
        let _page = client.getRecordsPage(page, size)?;

        client.deleteRecord(id)?;
        Ok(())
    }
}
